package com.franklin.ideaplugin.easytesting.rpc.dubbo;

import com.franklin.ideaplugin.api.classloader.ProjectClassLoader;
import com.franklin.ideaplugin.api.classloader.ProjectMethodInvoker;
import com.franklin.ideaplugin.easytesting.common.entity.MethodInvokeData;
import com.franklin.ideaplugin.easytesting.common.utils.MethodUtils;
import com.intellij.psi.PsiMethod;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @author Ye Junhui
 * @since 2023/8/27
 */
public class DubboInvoker {

    private final ProjectClassLoader projectClassLoader;

    private final Integer port;

    /**
     * dubbo 配置
     */
    private final DubboConfig dubboConfig;

    private final String interfaceClassName;

    private ProjectMethodInvoker referenceInvoker;

    private boolean init = false;

    public DubboInvoker(
            ProjectClassLoader projectClassLoader,
            Integer port,
            DubboConfig dubboConfig,
            String interfaceClassName
    ) {
        this.projectClassLoader = projectClassLoader;
        this.port = port;
        this.dubboConfig = dubboConfig;
        this.interfaceClassName = interfaceClassName;
    }

    public Object invoke(MethodInvokeData methodInvokeData) {
        try {

            // 注意：ReferenceConfig为重对象，内部封装了与注册中心的连接，以及与服务提供方的连接
            Class<?> interfaceClass = projectClassLoader.loadClass(interfaceClassName);

            Object invoker = null;

            if (Objects.isNull(referenceInvoker)) {
                // 当前应用配置
                ProjectMethodInvoker appConfigInvoker = new ProjectMethodInvoker(projectClassLoader, "com.alibaba.dubbo.config.ApplicationConfig");
                appConfigInvoker.invoke("setName", new Class[]{String.class}, new Object[]{"easy-testing-dubbo-consumer"});
                appConfigInvoker.invoke("setQosPort", new Class[]{Integer.class}, new Object[]{port});

                // 连接注册中心配置
                ProjectMethodInvoker registryInvoker = new ProjectMethodInvoker(projectClassLoader, "com.alibaba.dubbo.config.RegistryConfig");
                registryInvoker.invoke("setAddress", new Class[]{String.class}, new Object[]{this.dubboConfig.getRegistryAddress()});

                // 引用远程服务
                referenceInvoker = new ProjectMethodInvoker(projectClassLoader, "com.alibaba.dubbo.config.ReferenceConfig");
                referenceInvoker.invoke("setApplication", new String[]{"com.alibaba.dubbo.config.ApplicationConfig"}, new Object[]{appConfigInvoker.getTarget()});
                referenceInvoker.invoke("setRegistry", new String[]{"com.alibaba.dubbo.config.RegistryConfig"}, new Object[]{registryInvoker.getTarget()});
                referenceInvoker.invoke("setInterface", new Class[]{Class.class}, new Object[]{interfaceClass});
                referenceInvoker.invoke("setVersion", new Class[]{String.class}, new Object[]{this.dubboConfig.getVersion()});
                referenceInvoker.invoke("setGroup", new Class[]{String.class}, new Object[]{this.dubboConfig.getGroup()});
                referenceInvoker.invoke("setTimeout", new Class[]{Integer.class}, new Object[]{30000});
            }
            invoker = referenceInvoker.invoke("get", new Class[]{}, new Object[]{});
            this.init = true;
            Class<?>[] paramTypes = MethodUtils.resolveParamTypes(methodInvokeData);
            //参数值
            Object[] params = methodInvokeData.getParameterMap().values().stream()
                    .map(MethodUtils::parseValue)
                    .toArray();
            Method method = interfaceClass.getDeclaredMethod(methodInvokeData.getMethodName(), paramTypes);
            method.setAccessible(true);
            return method.invoke(invoker, params);
        } catch (Throwable e) {
            Throwable cause = e.getCause();
            if (Objects.isNull(cause)) {
                return e;
            }
            return cause;
        } finally {

        }
    }

    public boolean isInit(){
        return this.init;
    }

    public void close() {
        if (Objects.nonNull(referenceInvoker)) {
            try {
                referenceInvoker.invoke("destroy", new Class[]{}, new Object[]{});
            } catch (Exception exception) {
                exception.printStackTrace();
            }
        }
    }

    public DubboConfig getDubboConfig() {
        return dubboConfig;
    }

    public String getInterfaceClassName() {
        return interfaceClassName;
    }
}
